package com.ar4j.handler;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import net.sf.cglib.proxy.MethodProxy;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.springframework.dao.IncorrectResultSizeDataAccessException;

import com.ar4j.ActiveRecord;
import com.ar4j.NamedSingletonActiveRecordFactory;
import com.ar4j.ActiveRecordJsonWrapper;
import com.ar4j.ActiveRecordContext;
import com.ar4j.ActiveRecordMetadata;
import com.ar4j.pagination.PaginationAndSort;
import com.ar4j.util.ClassUtils;
import com.ar4j.util.CollectionUtils;

/**
 * Factory class that can generate method handlers for a given method name
 */
public class ActiveRecordMethodHandlerFactory {
  private static final Map<String, ActiveRecordMethod> METHOD_NAME_MAP = getMethodNameMap();
  public static final NoOpMethodHandler STATELESS_NO_OP_HANDLER = new NoOpMethodHandler();
  public static final ReadOnlyAwareDelegatingMethodHandler STATELESS_READ_ONLY_NO_OP_HANDLER = new ReadOnlyAwareDelegatingMethodHandler(STATELESS_NO_OP_HANDLER);
  
  
  private ActiveRecordMethodHandlerFactory() {
    // STATIC FACTORY CLASS - NO CONSTRUCTOR FOR YOU!
  }
  
  /**
   * @return a handler to be used for active record's base class's property getter methods
   */
  public static IMethodHandler getHandlerForPropertyGetter() {
    return STATELESS_NO_OP_HANDLER;
  }
  
  /**
   * @return a handler to be used for active record's base class's property setter methods
   */
  public static IMethodHandler getHandlerForPropertySetter() {
    return STATELESS_READ_ONLY_NO_OP_HANDLER;
  }
  
  /**
   * Find or create a handler for the given method name, within the context of the provided
   * metadata instance.
   * 
   * @throws IllegalArgumentException if the method cannot be identified as one belonging to the ActiveRecord
   * API or a handler cannot be found for the method.
   */
  public static IMethodHandler getHandlerForMethod(String methodName, ActiveRecordMetadata metadata) {
    
    // if the method is not recognized it must be a dynamic lookup method, otherwise process the recognized method type
    ActiveRecordMethod arMethod = METHOD_NAME_MAP.get(methodName);
    IMethodHandler out = null;
    if(arMethod == null) {
      out = getDynamicLookupMethodHandler(methodName, metadata);
    } else {
    
      // if there isn't a default, it must be a special case, instantiate the correct handler
      out = arMethod.getDefaultHandler();
      if(out == null) {
        switch(arMethod) {
          case FIND: out = new FindMethodHandler(); break;
          case COUNT: out = new CountMethodHandler(); break;
          case SAVE: out = new ReadOnlyAwareDelegatingMethodHandler(new SaveMethodHandler()); break;
          case DELETE: out = new ReadOnlyAwareDelegatingMethodHandler(new DeleteMethodHandler()); break;
        }
      }
    }
    
    if(out == null) {
      throw new IllegalArgumentException("No handler for: " + methodName + " in " + metadata.getBaseClass());
    }
    
    return out;
  }
  
  /**
   * Create a handler that is able to handle simple methods that retrieve data from an  
   * active record's metadata. These include:
   * 
   * getFactoryName
   * getBaseClass
   * getPropertyNames
   * getPropertyType
   * hasProperty
   * getIdentifierPropertyName
   * getTableName
   * getPropertyTypeConverter
   * getContext
   * 
   */
  protected static IMethodHandler getMetadataRetrievingHandler(final ActiveRecordMethod arMethod) {
    return new IMethodHandler() {
      @Override
      public Object intercept(ActiveRecordMetadata metadata, ActiveRecordContext context, Object target, Method method, Object[] args, MethodProxy proxy) 
        throws Throwable {
        
        Object out = null;
        switch(arMethod) {
          case GET_FACTORY_NAME: out = metadata.getFactoryName(); break;
          case GET_BASE_CLASS: out = metadata.getBaseClass(); break;
          case GET_PROPERTY_NAMES: out = metadata.getPropertyNames(); break;
          case GET_PROPERTY_TYPE: out = metadata.getPropertyTypeMap().get((String) args[0]); break;
          case HAS_PROPERTY: out = metadata.getPropertyNames().contains((String) args[0]); break;
          case IS_PROPERTY_READABLE: out = metadata.getBackingStoreReadableProperties().contains((String) args[0]); break;
          case IS_PROPERTY_WRITABLE: out = metadata.getBackingStoreWritableProperties().contains((String) args[0]); break;
          case GET_IDENTIFIER_NAME: out = metadata.getIdentifierPropertyName(); break;
          case GET_IDENTIFIER_TYPE: out = metadata.getPropertyTypeMap().get(metadata.getIdentifierPropertyName()); break;
          case GET_TABLE_NAME: out = metadata.getTableName(); break;
          case GET_TYPE_CONVERTER: out = metadata.getPropertyTypeConvertorMap().get((String) args[0]); break;
          case GET_CONTEXT: out = context; break;
        }
        
        return out;
      }
    };
  }
  
  /**
   * Create a handler that is able to retrieve an active record's property value via it's
   * read method.
   */
  protected static IMethodHandler getPropertyRetrievingHandler() {
    return new IMethodHandler() {
      @Override
      public Object intercept(ActiveRecordMetadata metadata, ActiveRecordContext context, Object target, Method method, Object[] args, MethodProxy proxy) 
        throws Throwable {
        
        // first arg must be the property name
        String name = (String) args[0];
        
        // find the read method, error out if there is none
        Method readMethod = metadata.getPropertyReadMethods().get(name);
        if(readMethod == null) {
          throw new IllegalArgumentException("Cannot get property value for " + name + " in " + metadata.getBaseClass() + " because the read method is missing");
        }
        
        // invoke and get the value
        Object value = readMethod.invoke(target);
        
        // coerce to a type if we were given one
        if(args.length > 1) {
          Class<?> requiredType = (Class<?>) args[1];
          value = ClassUtils.coerceValueToType(value, requiredType);
        }
        
        return value;
      }
    };
  }
  
  /**
   * Create a handler that is able to set an active record's property value via it's
   * write method.
   */
  protected static IMethodHandler getPropertySettingHandler() {
    IMethodHandler handler =  new IMethodHandler() {
      @Override
      public Object intercept(ActiveRecordMetadata metadata, ActiveRecordContext context, Object target, Method method, Object[] args, MethodProxy proxy) 
        throws Throwable {
        
        // first arg must be the property name
        String name = (String) args[0];
        
        // find the write method, error out if there is none
        Method writeMethod = metadata.getPropertyWriteMethods().get(name);
        if(writeMethod == null) {
          throw new IllegalArgumentException("Cannot set property value for " + name + " in " + metadata.getBaseClass() + " because the write method is missing");
        }
        
        // invoke and get the value
        writeMethod.invoke(target, args[1]);
        
        // always return the target itself
        return target;
      }
    };
    
    return new ReadOnlyAwareDelegatingMethodHandler(handler);
  }
  
  /**
   * Create a handler that is able to build a map of an active record's property values (keyed by their name)
   */
  protected static IMethodHandler getPropertyMapHandler() {
    return new IMethodHandler() {
      @Override
      public Object intercept(ActiveRecordMetadata metadata, ActiveRecordContext context, Object target, Method method, Object[] args, MethodProxy proxy) 
        throws Throwable {
        
        // build a map from the read method entry set
        Map<String, Object> out = new HashMap<String, Object>();
        for(Map.Entry<String, Method> entry : metadata.getPropertyReadMethods().entrySet()) {
          Object value = entry.getValue().invoke(target);
          out.put(entry.getKey(), value);
        }
        
        return out;
      }
    };
  }
  
  /**
   * Create a handler that is able to return a json wrapper for an active record
   */
  protected static IMethodHandler getJsonWrappingHandler() {
    return new IMethodHandler() {
      @Override
      public Object intercept(ActiveRecordMetadata metadata, ActiveRecordContext context, Object target, Method method, Object[] args, MethodProxy proxy) 
        throws Throwable {
        
        return new ActiveRecordJsonWrapper((ActiveRecord<?>) target);
      }
    };
  }
  
  /**
   * Create a handler that is able to handle an active record's equals method
   */
  protected static IMethodHandler getEqualsHandler(final boolean ignoreIdField) {
    return new IMethodHandler() {
      @Override
      public Object intercept(ActiveRecordMetadata metadata, ActiveRecordContext context, Object target, Method method, Object[] args, MethodProxy proxy) 
        throws Throwable {
        
        ActiveRecord<?> lhs = (ActiveRecord<?>) target;
        ActiveRecord<?> rhs = (ActiveRecord<?>) args[0];
        
        if (lhs == rhs) { return true; }
        if (lhs == null || rhs == null) { return false; }
        if(!lhs.getBaseClass().equals(rhs.getBaseClass())) { return false; }
        
        EqualsBuilder builder = new EqualsBuilder();
        for(String property : metadata.getPropertyNames()) {
          if(ignoreIdField && metadata.getIdentifierPropertyName().equals(property)) { continue; }
          builder.append(lhs.getProperty(property), rhs.getProperty(property));
        }
        
        return builder.isEquals();
      }
    };
  }
  
  /**
   * Creates a handler that is able to handle an active record's toString method, providing a short string
   * representation of a record's state (property values)
   */
  protected static IMethodHandler getToStringHandler() {
    return new IMethodHandler() {
      @Override
      public Object intercept(ActiveRecordMetadata metadata, ActiveRecordContext context, Object target, Method method, Object[] args, MethodProxy proxy) 
        throws Throwable {
        
        ActiveRecord<?> record = (ActiveRecord<?>) target;
        StringBuffer buffer = new StringBuffer();
        buffer.append(record.getBaseClass().getName()).append("{");
        boolean first = true;
        for(String name : metadata.getPropertyNames()) {
          if(!first) {
            buffer.append(",");
          } else {
            first = false;
          }
          
          buffer.append(name).append("=").append(record.getProperty(name));
        }
        buffer.append("}");
        
        return buffer.toString();
      }
    };
  }
  
  /**
   * Create a handler that is able to calculate an active record's hash code.
   * There is a special exception to how the hashCode is calculated for the active record's
   * property values. For all non enum values the hashCode method is executed on non null values.
   * For enums, the hash code of the enum's name is used. 
   */
  protected static IMethodHandler getHashCodeHandler() {
    return new IMethodHandler() {
      @Override
      public Object intercept(ActiveRecordMetadata metadata, ActiveRecordContext context, Object target, Method method, Object[] args, MethodProxy proxy) 
        throws Throwable {
        
        ActiveRecord<?> record = (ActiveRecord<?>) target;
        HashCodeBuilder builder = new HashCodeBuilder();
        for(String name : metadata.getPropertyNames()) {
          Object value = record.getProperty(name);
          
          // enums are a special case, their hashCode is not consistent from VM start up to start up
          // instead we will use their name's hashCode
          if(value != null && value.getClass().isEnum()) {
            value = ((Enum<?>) value).name();
          }
          
          builder.append(value);
        }
        
        return builder.toHashCode();
      }
    };
  }
  
  /**
   * Creates a handler that is able to clone an active record
   */
  protected static IMethodHandler getCloneRecordHandler() {
    return new IMethodHandler() {
      @SuppressWarnings("unchecked")
      @Override
      public Object intercept(ActiveRecordMetadata metadata, ActiveRecordContext context, Object target, Method method, Object[] args, MethodProxy proxy) 
        throws Throwable {
        
        ActiveRecord record = NamedSingletonActiveRecordFactory.getFactory(metadata.getFactoryName()).getActiveRecord((ActiveRecord) target);
        record.getContext().setValuesFrom(context);
        return record;
      }
    };
  }
  
  /**
   * Creates a handler that can retrieve an active record's identifier value
   */
  protected static IMethodHandler getIdentifierHandler() {
    return new IMethodHandler() {
      @Override
      public Object intercept(ActiveRecordMetadata metadata, ActiveRecordContext context, Object target, Method method, Object[] args, MethodProxy proxy) 
        throws Throwable {
        
        ActiveRecord<?> record = (ActiveRecord<?>) target;
        return record.getProperty(record.getIdentifierPropertyName());
      }
    };
  }
  
  /**
   * Creates a handler that is able to handle an active record's isNewRecord method,
   * performing a count operation on the backing store (using the identifier value) to
   * check if the record exists or not.
   */
  protected static IMethodHandler getIsNewRecordHandler() {
    return new IMethodHandler() {
      @Override
      public Object intercept(ActiveRecordMetadata metadata, ActiveRecordContext context, Object target, Method method, Object[] args, MethodProxy proxy) 
        throws Throwable {
        
        // null IDs are new by default
        ActiveRecord<?> record = (ActiveRecord<?>) target;
        Object id = record.getIdentifier();
        if(id == null) {
          return true;
        }
        
        long oldCount = record.count(CollectionUtils.getSingleValueMap(metadata.getIdentifierPropertyName(), id));
        return (oldCount == 0);
      }
    };
  }
  
  /**
   * Creates a handler that is able to retrieve a record by its identifier value.
   * Delegates to a standard find method, with optional locking for update
   */
  protected static IMethodHandler getFindByIdentifierHandler() {
    return new IMethodHandler() {
      @Override
      public Object intercept(ActiveRecordMetadata metadata, ActiveRecordContext context, Object target, Method method, Object[] args, MethodProxy proxy) 
        throws Throwable {
        
        Map<String, Object> parameters = CollectionUtils.getSingleValueMap(metadata.getIdentifierPropertyName(), args[0]);
        boolean lockForUpdate = (args.length > 1 ? (Boolean) args[1] : false);
        
        ActiveRecord<?> record = (ActiveRecord<?>) target;
        Collection<?> results = record.find(parameters, PaginationAndSort.NONE, lockForUpdate);
        if(results.size() != 1) {
          throw new IncorrectResultSizeDataAccessException("Expected a single record with the given identifier", 1, results.size());
        }
        
        return results.iterator().next();
      }
    };
  }
  
  /**
   * Creates a handler that is able to reload an active record from the backing store.
   * If the record is new (isNewRecord) an exception will be thrown, otherwise the record
   * will be reloaded from the backing store.
   */
  protected static IMethodHandler getReloadHandler() {
    return new IMethodHandler() {
      @Override
      public Object intercept(ActiveRecordMetadata metadata, ActiveRecordContext context, Object target, Method method, Object[] args, MethodProxy proxy) 
        throws Throwable {
        
        if(context.getSimpleTemplate() == null) {
          throw new IllegalStateException("Cannot execute reload on an active record that does not have a datasource set");
        }
        
        ActiveRecord<?> record = (ActiveRecord<?>) target;
        if(record.isNewRecord()) {
          throw new IllegalStateException("Cannot execute reload on a new active record, it does not have an identifier");
        }
        
        Map<String, Object> findParameters = CollectionUtils.getSingleValueMap(record.getIdentifierPropertyName(), record.getIdentifier());
        Collection<?> loaded = record.find(findParameters);
        if(loaded.size() != 1) {
          throw new IncorrectResultSizeDataAccessException("Expected a single record on reload", 1, loaded.size());
        }
        
        ActiveRecord<?> other = (ActiveRecord<?>) loaded.iterator().next();
        for(String property : metadata.getPropertyNames()) {
          record.setProperty(property, other.getProperty(property));
        }
        
        return record;
      }
    };
  }
  
  /**
   * Creates a handler that is able to handle findAll operations of an active record. 
   * Internally executes the find operation on the record itself with no parameters.
   */
  protected static IMethodHandler getFindAllHandler() {
    return new IMethodHandler() {
      @SuppressWarnings("unchecked")
      @Override
      public Object intercept(ActiveRecordMetadata metadata, ActiveRecordContext context, Object target, Method method, Object[] args, MethodProxy proxy) 
        throws Throwable {
        
        // get record and pagination, if its there
        ActiveRecord<?> record = (ActiveRecord<?>) target;
        PaginationAndSort pagination = PaginationAndSort.NONE;
        if(args.length > 0) {
          pagination = (PaginationAndSort) args[0];
        }
        
        return record.find((Map<String, ?>) CollectionUtils.EMPTY_MAP, pagination);
      }
    };
  }
  
  /**
   * Creates a handler that is able to handle countAll operations of an active record. 
   * Internally executes the count operation on the record itself with no parameters.
   */
  protected static IMethodHandler getCountAllHandler() {
    return new IMethodHandler() {
      @SuppressWarnings("unchecked")
      @Override
      public Object intercept(ActiveRecordMetadata metadata, ActiveRecordContext context, Object target, Method method, Object[] args, MethodProxy proxy) 
        throws Throwable {
        
        ActiveRecord<?> record = (ActiveRecord<?>) target;
        return record.count((Map<String, ?>) CollectionUtils.EMPTY_MAP);
      }
    };
  }
  
  /**
   * Creates a handler that is able to handle the exists operations of an active record. 
   * Internally executes the count operation on the record itself with the identifier name as a parameter.
   */
  protected static IMethodHandler getExistsHandler() {
    return new IMethodHandler() {
      @Override
      public Object intercept(ActiveRecordMetadata metadata, ActiveRecordContext context, Object target, Method method, Object[] args, MethodProxy proxy) 
        throws Throwable {
        
        long expectedResult = 1;
        if(args[0] != null && Collection.class.isAssignableFrom(args[0].getClass())) {
          expectedResult = ((Collection<?>) args[0]).size();
        }
        
        ActiveRecord<?> record = (ActiveRecord<?>) target;
        Map<String, ?> parameters = CollectionUtils.getSingleValueMap(record.getIdentifierPropertyName(), args[0]);
        long result = record.count(parameters);
        return (result == expectedResult);
      }
    };
  }
  
  /**
   * Creates a handler for a dynamic lookup method (findBy, findAllBy, countBy)
   */
  protected static IMethodHandler getDynamicLookupMethodHandler(String methodName, ActiveRecordMetadata metadata) {

    // find the method and make sure its only one
    Method method = null;
    for(Method candidate : metadata.getBaseClass().getMethods()) {
      if(candidate.getName().equals(methodName)) {
        if(method == null) {
          method = candidate;
        } else {
          throw new IllegalArgumentException("Cannot handle dynamic lookup method for an overriden method. " + methodName + 
              " must have a single implementation in " + metadata.getBaseClass().getName());
        }
      }
    }
    
    // get the right handler
    LookupMethodMetadata lookup = LookupMethodMetadataFactory.getLookupMethodMetadata(method, metadata);
    return new LookupMethodHandler(lookup);
  }
  
  /**
   * Creates a handler to set the readOnly flag on a record's context
   */
  protected static IMethodHandler getSetReadOnlyHandler() {
    return new IMethodHandler() {
      @Override
      public Object intercept(ActiveRecordMetadata metadata, ActiveRecordContext context, Object target, Method method, Object[] args, MethodProxy proxy) 
        throws Throwable {
        
        ActiveRecord<?> record = (ActiveRecord<?>) target;
        record.getContext().setReadOnly((Boolean) args[0]);
        return record;
      }
    };
  }
  
  /**
   * Creates a handler to retrieve the readOnly flag from a record's context
   */
  protected static IMethodHandler getIsReadOnlyHandler() {
    return new IMethodHandler() {
      @Override
      public Object intercept(ActiveRecordMetadata metadata, ActiveRecordContext context, Object target, Method method, Object[] args, MethodProxy proxy) 
        throws Throwable {
        
        ActiveRecord<?> record = (ActiveRecord<?>) target;
        return record.getContext().isReadOnly();
      }
    };
  }
  
  /**
   * Generates a map from method names to ActiveRecordMethod enum values. The enum values
   * themselves contain a reference to a method name for each value, but a reverse map is 
   * needed for quick lookups when generating handlers
   */
  private static Map<String, ActiveRecordMethod> getMethodNameMap() {
    Map<String, ActiveRecordMethod> out = new HashMap<String, ActiveRecordMethod>();
    for(ActiveRecordMethod method : ActiveRecordMethod.values()) {
      out.put(method.getMethodName(), method);
    }
    
    return out;
  }

  /**
   * An enum whose values represent each active record method that the handler factory
   * is able to generate handlers for. Each value has the name of the method and a possible
   * default handler (singleton).
   */
  private static enum ActiveRecordMethod {
    GET_FACTORY_NAME          ("getFactoryName"),
    GET_BASE_CLASS            ("getBaseClass"),
    GET_PROPERTY_NAMES        ("getPropertyNames"),
    GET_PROPERTY_TYPE         ("getPropertyType"),
    HAS_PROPERTY              ("hasProperty"),
    IS_PROPERTY_READABLE      ("isPropertyBackingStoreReadable"),
    IS_PROPERTY_WRITABLE      ("isPropertyBackingStoreWritable"),
    GET_PROPERTY              ("getProperty"),
    SET_PROPERTY              ("setProperty"),
    GET_PROPERTY_MAP          ("getPropertyMap"),
    WRITE_REPLACE             ("writeReplace"),
    WRAP_IN_JSON              ("wrapInJson"),
    EQUALS                    ("equals"),
    IS_SAME_CONTENT           ("isSameContent"),
    TO_STRING                 ("toString"),
    HASH_CODE                 ("hashCode"),
    GET_IDENTIFIER_NAME       ("getIdentifierPropertyName"),
    GET_IDENTIFIER_TYPE       ("getIdentifierPropertyType"),
    GET_IDENTIFIER            ("getIdentifier"),
    CLONE_RECORD              ("cloneRecord"),
    IS_NEW_RECORD             ("isNewRecord"),
    GET_TABLE_NAME            ("getTableName"),
    GET_TYPE_CONVERTER        ("getPropertyTypeConverter"),
    GET_CONTEXT               ("getContext"),
    FIND                      ("find"),
    FIND_BY_IDENTIFIER        ("findByIdentifier"),
    COUNT                     ("count"),
    RELOAD                    ("reload"),
    SAVE                      ("save"),
    DELETE                    ("delete"),
    FIND_ALL                  ("findAll"),
    COUNT_ALL                 ("countAll"),
    EXISTS                    ("exists"),
    SET_READ_ONLY             ("setReadOnly"),
    IS_READ_ONLY              ("isReadOnly"),
    ;
    
    private String methodName;
    private IMethodHandler defaultHandler;
    
    private ActiveRecordMethod(String methodName) { this.methodName = methodName; }
    public String getMethodName() { return methodName; }
    public IMethodHandler getDefaultHandler() { return defaultHandler; }
    private void setDefaultMehodHandler(IMethodHandler defaultHandler) { this.defaultHandler = defaultHandler; }
    
    // this is performed in a static block to allow the use of defined enum values when initializing handlers
    static {
      GET_FACTORY_NAME.setDefaultMehodHandler(ActiveRecordMethodHandlerFactory.getMetadataRetrievingHandler(GET_FACTORY_NAME));
      GET_BASE_CLASS.setDefaultMehodHandler(ActiveRecordMethodHandlerFactory.getMetadataRetrievingHandler(GET_BASE_CLASS));
      GET_PROPERTY_NAMES.setDefaultMehodHandler(ActiveRecordMethodHandlerFactory.getMetadataRetrievingHandler(GET_PROPERTY_NAMES));
      GET_PROPERTY_TYPE.setDefaultMehodHandler(ActiveRecordMethodHandlerFactory.getMetadataRetrievingHandler(GET_PROPERTY_TYPE));
      HAS_PROPERTY.setDefaultMehodHandler(ActiveRecordMethodHandlerFactory.getMetadataRetrievingHandler(HAS_PROPERTY));
      IS_PROPERTY_READABLE.setDefaultMehodHandler(ActiveRecordMethodHandlerFactory.getMetadataRetrievingHandler(IS_PROPERTY_READABLE));
      IS_PROPERTY_WRITABLE.setDefaultMehodHandler(ActiveRecordMethodHandlerFactory.getMetadataRetrievingHandler(IS_PROPERTY_WRITABLE));
      GET_PROPERTY.setDefaultMehodHandler(ActiveRecordMethodHandlerFactory.getPropertyRetrievingHandler());
      SET_PROPERTY.setDefaultMehodHandler(ActiveRecordMethodHandlerFactory.getPropertySettingHandler());
      GET_PROPERTY_MAP.setDefaultMehodHandler(ActiveRecordMethodHandlerFactory.getPropertyMapHandler());
      WRITE_REPLACE.setDefaultMehodHandler(ActiveRecordMethodHandlerFactory.getJsonWrappingHandler());
      WRAP_IN_JSON.setDefaultMehodHandler(WRITE_REPLACE.getDefaultHandler());
      EQUALS.setDefaultMehodHandler(ActiveRecordMethodHandlerFactory.getEqualsHandler(false));
      IS_SAME_CONTENT.setDefaultMehodHandler(ActiveRecordMethodHandlerFactory.getEqualsHandler(true));
      TO_STRING.setDefaultMehodHandler(ActiveRecordMethodHandlerFactory.getToStringHandler());
      HASH_CODE.setDefaultMehodHandler(ActiveRecordMethodHandlerFactory.getHashCodeHandler());
      GET_IDENTIFIER_NAME.setDefaultMehodHandler(ActiveRecordMethodHandlerFactory.getMetadataRetrievingHandler(GET_IDENTIFIER_NAME));
      GET_IDENTIFIER_TYPE.setDefaultMehodHandler(ActiveRecordMethodHandlerFactory.getMetadataRetrievingHandler(GET_IDENTIFIER_TYPE));
      GET_IDENTIFIER.setDefaultMehodHandler(ActiveRecordMethodHandlerFactory.getIdentifierHandler());
      CLONE_RECORD.setDefaultMehodHandler(ActiveRecordMethodHandlerFactory.getCloneRecordHandler());
      IS_NEW_RECORD.setDefaultMehodHandler(ActiveRecordMethodHandlerFactory.getIsNewRecordHandler());
      GET_TABLE_NAME.setDefaultMehodHandler(ActiveRecordMethodHandlerFactory.getMetadataRetrievingHandler(GET_TABLE_NAME));
      GET_TYPE_CONVERTER.setDefaultMehodHandler(ActiveRecordMethodHandlerFactory.getMetadataRetrievingHandler(GET_TYPE_CONVERTER));
      GET_CONTEXT.setDefaultMehodHandler(ActiveRecordMethodHandlerFactory.getMetadataRetrievingHandler(GET_CONTEXT));
      FIND.setDefaultMehodHandler(null); // marker to show that there is no default
      FIND_BY_IDENTIFIER.setDefaultMehodHandler(ActiveRecordMethodHandlerFactory.getFindByIdentifierHandler());
      COUNT.setDefaultMehodHandler(null); // marker to show that there is no default
      RELOAD.setDefaultMehodHandler(ActiveRecordMethodHandlerFactory.getReloadHandler());
      SAVE.setDefaultMehodHandler(null); // marker to show that there is no default
      DELETE.setDefaultMehodHandler(null); // marker to show that there is no default
      FIND_ALL.setDefaultMehodHandler(ActiveRecordMethodHandlerFactory.getFindAllHandler());
      COUNT_ALL.setDefaultMehodHandler(ActiveRecordMethodHandlerFactory.getCountAllHandler());
      EXISTS.setDefaultMehodHandler(ActiveRecordMethodHandlerFactory.getExistsHandler());
      SET_READ_ONLY.setDefaultMehodHandler(ActiveRecordMethodHandlerFactory.getSetReadOnlyHandler());
      IS_READ_ONLY.setDefaultMehodHandler(ActiveRecordMethodHandlerFactory.getIsReadOnlyHandler());
    }
  }
}
